home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 7: Sunsite / Linux Cubed Series 7 - Sunsite Vol 1.iso / system / news / inn1.000 / inn1.4sec-linux-src.tar / inn / samples / scanspool < prev    next >
Text File  |  1993-01-29  |  13KB  |  462 lines

  1. #!/usr/bin/perl
  2. # @(#)scanspool.pl    1.20 4/6/92 00:47:35
  3. #
  4. # Written by:  Landon Curt Noll        (chongo was here  /\../\)
  5. #
  6. # This code is placed in the public domain.
  7. #
  8. # scanspool - perform a big scan over all articles in /usr/spool/news
  9. #
  10. # usage:
  11. #    scanspool [-a active_file] [-s spool_dir] [-v] [-c] [-n]
  12. #
  13. #    -a active_file    active file to use (default /usr/lib/news/active)
  14. #    -s spool_dir    spool tree (default /usr/spool/news)
  15. #    -v         verbose mode
  16. #            verbose messages begin with a tab
  17. #            show articles found in non-active directories
  18. #    -c            check article filenames, don't scan the articles
  19. #    -n            don't throttle innd
  20. #
  21. # NOTE: This take a while, -v is a good thing if you want to know
  22. #    how far this program has progressed.
  23. #
  24. # This program will scan first the active file, noting problems such as:
  25. #
  26. #    malformed line
  27. #    group aliased to a non-existent group
  28. #    group aliased to a group tat is also aliased
  29. #
  30. # Then it will examine all articles under your news spool directory,
  31. # looking for articles that:
  32. #
  33. #    basename that starts with a leading 0
  34. #    basename that is out of range with the active file
  35. #    does not contain a Newsgroups: line
  36. #    article that is all header and no text
  37. #    is in a directory for which there is no active group
  38. #    article that is in a group to which it does not belong
  39. #
  40. # Scanspool understands aliased groups.  Thus, if an article is posted
  41. # to foo.old.name that is aliases to foo.bar, it will be expected to
  42. # be found under foo.bar and not foo.old.name.
  43. #
  44. # Any group that is of type 'j' or 'x' (4th field of the active file)
  45. # will be allowed to show up under the junk group.
  46. #
  47. # Scanspool assumes that the path of a valid newsgroup's directory
  48. # from the top of the spool tree will not contain any "." character.
  49. # Thus, directories such as out.going, tmp.dir, in.coming and
  50. # news.archive will not be searched.  This program also assumes that
  51. # article basenames contain only decimal digits.  Last, files under
  52. # the top level directory "lost+found" are not scanned.
  53. #
  54. # The output of scanspool will start with one of 4 forms:
  55. #
  56. #    FATAL:         fatal or internal error             (to stderr)
  57. #
  58. #    WARN:         active or article format problem,        (to stderr)
  59. #            group alias problem, find error,
  60. #            article open error
  61. #
  62. #    path/123:        basename starts with 0,            (to stdout)
  63. #            article number out of range,
  64. #            article in the wrong directory,
  65. #            article in directory not related to
  66. #                an active non-aliases newsgroup
  67. #
  68. #    \t ...        verbose message starting with a tab        (to stdout)
  69.  
  70.  
  71. # Data structures
  72. #
  73. # $gname2type{$name}
  74. #    $name    - newsgroup name in foo.dot.form
  75. #    produces  => 4th active field  (y, n, x, ...)
  76. #          alias type is "=", not "=foo.bar"
  77. #
  78. # $realgname{$name}
  79. #    $name      - newsgroup name in foo.dot.form
  80. #    produces  => newsgroup name in foo.dot.form
  81. #          if type is =, this will be a.b, not $name
  82. #
  83. # $lowart{$name}
  84. #    $name      - newsgroup name in foo.dot.form
  85. #    produces  => lowest article allowed in the group
  86. #          if type is =, this is not valid
  87. #
  88. # $highart{$name}
  89. #    $name      - newsgroup name in foo.dot.form
  90. #    produces  => highest article allowed in the group
  91. #          if type is =, this is not valid
  92. #          If $highart{$name} < $lowart{$name},
  93. #          then the group should be empty
  94.  
  95. # perl requirements
  96. #
  97. require "getopts.pl";
  98.  
  99. # setup non-buffered stdout and stderr
  100. #
  101. select(STDERR);
  102. $|=1;
  103. select(STDOUT);
  104. $|=1;
  105.  
  106. # global constants
  107. #
  108. $prog = $0;                 # our name
  109. # =()<$spool = "@<_PATH_SPOOL>@";>()=   # top of where articles are filed
  110. $spool = "/news/spool";
  111. # =()<$active = "@<_PATH_ACTIVE>@";>()=    # inn active file, list of groups
  112. $active = "/news/lib/active";
  113. # =()<$ctlinnd = "@<_PATH_NEWSBIN>@/ctlinnd";>()=
  114. $ctlinnd = "/news/bin/ctlinnd";
  115. $reason = "running scanspool";        # throttle reason
  116.  
  117. # parse args
  118. #
  119. &Getopts("a:s:vcn");
  120. $active = $opt_a if (defined($opt_a));
  121. $spool = $opt_s if (defined($opt_s));
  122.  
  123. # throttle innd unless -n
  124. #
  125. if (! defined($opt_n)) {
  126.     system("$ctlinnd throttle '$reason' >/dev/null 2>&1");
  127. }
  128.  
  129. # process the active file
  130. #
  131. &parse_active($active);
  132.  
  133. # check the spool directory
  134. #
  135. &check_spool($spool);
  136.  
  137. # unthrottle innd unless -n
  138. #
  139. if (! defined($opt_n)) {
  140.     system("$ctlinnd go '$reason' >/dev/null 2>&1");
  141. }
  142.  
  143. # all done
  144. exit(0);
  145.  
  146.  
  147. # parse_active - parse the active file
  148. #
  149. # From the active file, fill out the @gname2type (type of newsgroup)
  150. # and @realgname (real/non-aliased name of group), @lowart & @highart
  151. # (low and high article numbers).  This routine will also check for
  152. # aliases to missing groups or groups that are also aliases.
  153. #
  154. sub parse_active
  155. {
  156.     local ($active) = $_[0];    # the name of the active file to use
  157.     local (*ACTIVE);        # active file handle
  158.     local ($line);        # active file line
  159.     local ($name);        # name of newsgroup
  160.     local ($low);        # low article number
  161.     local ($high);        # high article number
  162.     local ($type);        # type of newsgroup (4th active field)
  163.     local ($field5);        # 5th active field (should not exist)
  164.     local ($dir);        # directory path of group from $spool
  165.     local ($alias);        # realname of an aliased group
  166.     local ($linenum);        # active file line number
  167.  
  168.     # if verbose (-v), say what we are doing
  169.     print "\tscanning $active\n" if defined($opt_v);
  170.  
  171.     # open the active file
  172.     open (ACTIVE, $active) || &fatal(1, "cannot open $active");
  173.  
  174.     # parse each line
  175.     $linenum = 0;
  176.     while ($line = <ACTIVE>) {
  177.  
  178.     # count the line
  179.     ++$linenum;
  180.  
  181.     # verify that we have a correct number of tokens
  182.     if ($line !~ /^\S+ 0*(\d+) 0*(\d+) \S+$/o) {
  183.         &problem("WARNING: active line is mal-formed at line $linenum");
  184.         next;
  185.     }
  186.     ($name, $high, $low, $type) = $line =~ /^(\S+) 0*(\d+) 0*(\d+) (\S+)$/o;
  187.  
  188.     # watch for duplicate entries
  189.     if (defined($realgname{$name})) {
  190.         &problem("WARNING: ignoring dup group: $name, at line $linenum");
  191.         next;
  192.     }
  193.  
  194.     # record which type it is
  195.     $gname2type{$name} = $type;
  196.  
  197.     # record the low and high article numbers
  198.     $lowart{$name} = $low;
  199.     $highart{$name} = $high;
  200.  
  201.     # determine the directory and real group name
  202.     if ($type eq "j" || $type eq "x") {
  203.         $dir = "junk";
  204.         $alias = $name;
  205.     } elsif ($type =~ /^=(.+)/o) {
  206.         $alias = $1;
  207.         ($dir = $alias) =~ s#\.#/#go;
  208.         $gname2type{$name} = "=";    # rename type to be just =
  209.     } else {
  210.         $dir = $name;
  211.         $dir =~ s#\.#/#go;
  212.         $alias = $name;
  213.     }
  214.     $realgname{$name} = $alias;
  215.     }
  216.  
  217.     # close the active file
  218.     close (ACTIVE);
  219.  
  220.     # be sure that any alias type is aliased to a real group
  221.     foreach $name (keys %realgname) {
  222.  
  223.     # skip if not an alias type
  224.     next if $gname2type{$name} ne "=";
  225.  
  226.     # be sure that the alias exists
  227.     $alias = $realgname{$name};
  228.     if (! defined($realgname{$alias})) {
  229.         &problem("WARNING: alias for $name: $alias, is not a group");
  230.         next;
  231.     }
  232.  
  233.     # be sure that the alias is not an alias of something else
  234.     if ($gname2type{$alias} eq "=") {
  235.         &problem("WARNING: alias for $name: $alias, is also an alias");
  236.         next;
  237.     }
  238.     }
  239. }
  240.  
  241.  
  242. # problem - report a problem to stdout
  243. #
  244. # Print a message to stdout.  Parameters are space separated.
  245. # A final newline is appended to it.
  246. #
  247. # usage:
  248. #    &problem(arg, arg2, ...)
  249. #
  250. sub problem
  251. {
  252.     local ($line);        # the line to write
  253.  
  254.     # print the line with the header and newline
  255.     $line = join(" ", @_);
  256.     print STDERR $line, "\n";
  257. }
  258.  
  259.  
  260. # fatal - report a fatal error to stderr and exit
  261. #
  262. # Print a message to stderr.  The message has the program name prepended
  263. # to it.  Parameters are space separated.  A final newline is appended
  264. # to it.  This function exists with the code of exitval.
  265. #
  266. # usage:
  267. #    &fatal(exitval, arg, arg2, ...)
  268. #
  269. sub fatal
  270. {
  271.     local ($exitval) = $_[0];    # what to exit with
  272.  
  273.     # firewall
  274.     if ($#_ < 1) {
  275.     print STDERR "FATAL: fatal called with only ", $#_-1, " arguments\n";
  276.     if ($#_ < 0) {
  277.         $exitval = -1;
  278.     }
  279.     }
  280.  
  281.     # print the error message
  282.     shift(@_);
  283.     $line = join(" ", @_);
  284.     print STDERR "$prog: ", $line, "\n";
  285.  
  286.     # unthrottle innd unless -n
  287.     #
  288.     if (! defined($opt_n)) {
  289.     system("$ctlinnd go '$reason' >/dev/null 2>&1");
  290.     }
  291.  
  292.     # exit
  293.     exit($exitval);
  294. }
  295.  
  296.  
  297. # check_spool - check the articles found in the spool directory
  298. #
  299. # This subroutine will check all articles found under the $spool directory.
  300. # It will examine only file path that do not contain any "." or whitespace
  301. # character, and whose basename is completely numeric.  Files under
  302. # lost+found will also be ignored.
  303. #
  304. # given:
  305. #    $spooldir  - top of /usr/spool/news article tree
  306. #
  307. sub check_spool
  308. {
  309.     local ($spooldir) = $_[0];    # top of article tree
  310.     local (*FILEFILE);        # pipe from the find files process
  311.     local ($filename);        # article pathname under $spool
  312.     local ($artgrp);        # group of an article
  313.     local ($artnum);        # article number in a group
  314.     local ($prevgrp);        # previous different value of $artgrp
  315.     local ($preverrgrp);    # previous non-active $artgrp
  316.     local (*ARTICLE);        # article handle
  317.     local ($aline);        # header line from an article
  318.     local (@group);        # array of groups from the Newsgroup header
  319.     local ($j);
  320.  
  321.     # if verbose, say what we are doing
  322.     print "\tfinding articles under $spooldir\n" if defined($opt_v);
  323.  
  324.     # move to the $spool directory
  325.     chdir $spooldir || &fatal(2, "cannot chdir to $spool");
  326.  
  327.     # start finding files
  328.     #
  329.     if (!open (FINDFILE,
  330.       "find . \\( -type f -o -type l \\) -name '[0-9]*' -print 2>&1 |")) {
  331.     &fatal(3, "cannot start find in $spool");
  332.     }
  333.  
  334.     # process each history line
  335.     #
  336.     while ($filename = <FINDFILE>) {
  337.  
  338.     # if the line contains find:, assume it is a find error and print it
  339.     chop($filename);
  340.     if ($filename =~ /find:\s/o) {
  341.         &problem("WARNING:", $filename);
  342.         next;
  343.     }
  344.  
  345.     # remove the \n and ./ that find put in our path
  346.     $filename =~ s#^\./##o;
  347.  
  348.     # skip is this path has a . in it (beyond a leading ./)
  349.     next if ($filename =~ /\./o);
  350.  
  351.     # skip if lost+found
  352.     next if ($filename =~ m:^lost+found/:o);
  353.  
  354.     # skip if not a numeric basename
  355.     next if ($filename !~ m:/\d+$:o);
  356.  
  357.     # get the article's newsgroup name (based on its path from $spool)
  358.     $artgrp = $filename;
  359.     $artgrp =~ s#/\d+$##o;
  360.     $artgrp =~ s#/#.#go;
  361.  
  362.     # if verbose (-v), then note if our group changed
  363.     if (defined($opt_v) && $artgrp ne $prevgrp) {
  364.         print "\t$artgrp\n";
  365.         $prevgrp = $artgrp;
  366.     }
  367.  
  368.     # note if the article is not in a directory that is used by
  369.     # a real (non-aliased) group in the active file
  370.     #
  371.     # If we complained about this dgroup before, don't complain again.
  372.     # If verbose, note files that could be removed.
  373.     #
  374.     if (!defined($gname2type{$artgrp}) || $gname2type{$artgrp} =~ /[=jx]/o){
  375.         if ($preverrgrp ne $artgrp) {
  376.         &problem("$artgrp: not an active group directory");
  377.         $preverrgrp = $artgrp;
  378.         }
  379.         if (defined($opt_v)) {
  380.         &problem("$filename: article found in non-active directory");
  381.         }
  382.         next;
  383.     }
  384.  
  385.     # check on the article number
  386.     $artnum = $filename;
  387.     $artnum =~ s#^.+/##o;
  388.     if ($artnum =~ m/^0/o) {
  389.         &problem("$filename: article basename starts with a 0");
  390.     }
  391.     if (defined($gname2type{$artgrp})) {
  392.         if ($lowart{$artgrp} > $highart{$artgrp}) {
  393.         &problem("$filename: active indicates group should be empty");
  394.         } elsif ($artnum < $lowart{$artgrp}) {
  395.         &problem("$filename: article number is too low");
  396.         } elsif ($artnum > $highart{$artgrp}) {
  397.         &problem("$filename: article number is too high");
  398.         }
  399.     }
  400.  
  401.     # if check filenames only (-c), then do nothing else with the file
  402.     next if (defined($opt_c));
  403.  
  404.     # don't open a control or junk, they can be from anywhere
  405.     next if ($artgrp eq "control" || $artgrp eq "junk");
  406.  
  407.     # try open the file
  408.     if (!open(ARTICLE, $filename)) {
  409.  
  410.         # the find is now gone (expired?), give up on it
  411.         &problem("WARNING: cannot open $filename");
  412.         next;
  413.     }
  414.  
  415.     # read until the Newsgroup header line is found
  416.     AREADLINE:
  417.     while ($aline = <ARTICLE>) {
  418.  
  419.         # catch the newsgroup: header
  420.         if ($aline =~ /^Newsgroups:\w*\W/io) {
  421.  
  422.         # convert $aline into a comma separated list of groups
  423.         $aline =~ s/^Newsgroups://io;
  424.         $aline =~ tr/ \t\n//d;
  425.  
  426.         # form an array of news groups
  427.         @group = split(",", $aline);
  428.  
  429.         # see if any groups in the Newsgroup list are our group
  430.         for ($j=0; $j <= $#group; ++$j) {
  431.  
  432.             # look at the group
  433.             if ($realgname{$group[$j]} eq $artgrp) {
  434.             # this article was posted to this group
  435.             last AREADLINE;
  436.             }
  437.         }
  438.  
  439.         # no group or group alias was found
  440.         &problem("$filename: does not belong in $artgrp");
  441.         last;
  442.  
  443.         # else watch for the end of the header
  444.         } elsif ($aline =~ /^\s*$/o) {
  445.  
  446.         # no Newsgroup: header found
  447.         &problem("WARNING: $filename: no Newsgroup header");
  448.         last;
  449.         }
  450.         if (eof(ARTICLE)) {
  451.         &problem("WARNING: $filename: EOF found while reading header");
  452.         }
  453.     }
  454.  
  455.     # close the article
  456.     close(ARTICLE);
  457.     }
  458.  
  459.     # all done with the find
  460.     close(FINDFILE);
  461. }
  462.